/**
 * Copyright (c) 2003 held jointly by the individual authors.
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *  > http://www.gnu.org/copyleft/lesser.html >
 * http://www.opensource.org/licenses/lgpl-license.php
 */
package net.mlw.vlh.adapter.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import net.mlw.vlh.DefaultListBackedValueList;
import net.mlw.vlh.ValueList;
import net.mlw.vlh.ValueListInfo;
import net.mlw.vlh.adapter.AbstractValueListAdapter;
import net.mlw.vlh.adapter.jdbc.objectWrapper.ResultSetDecorator;
import net.mlw.vlh.adapter.jdbc.util.ConnectionCreator;
import net.mlw.vlh.adapter.jdbc.util.JdbcUtil;
import net.mlw.vlh.adapter.jdbc.util.SqlPagingSupport;
import net.mlw.vlh.adapter.jdbc.util.StandardConnectionCreator;
import net.mlw.vlh.adapter.jdbc.util.StandardStatementBuilder;
import net.mlw.vlh.adapter.jdbc.util.StatementBuilder;
import net.mlw.vlh.adapter.util.ObjectValidator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This adapter handles the standard functionality of creating a query and
 * execution it...
 *
 * If you set validator, it will use special ResultSetDecorator.
 *
 * @see net.mlw.vlh.adapter.jdbc.objectWrapper.ResultSetDecorator
 * @author Matthew L. Wilson, Andrej Zachar
 * @version $Revision: 1.27 $ $Date: 2006/04/18 17:15:05 $
 */
public abstract class AbstractJdbcAdapter extends AbstractValueListAdapter
{
   /** Commons logger. */
   private static final Log LOGGER = LogFactory.getLog(AbstractJdbcAdapter.class);

   /** The sql to execute. */
   private String sql;

   /** Display generated sql on th standard output */
   private boolean showSql = false;

   /** The StatementBuilder to help generate a sql query. */
   private StatementBuilder statementBuilder = new StandardStatementBuilder();

   /** The ConnectionCreator to help SQL connection handling */
   private ConnectionCreator connectionCreator = new StandardConnectionCreator();

   /**
    * The validator for ResultSet's records.
    */
   private ObjectValidator _validator = null;

   /**
    * Helper to provide true in-database paging.
    */
   private SqlPagingSupport sqlPagingSupport;

   public AbstractJdbcAdapter()
   {}

   /**
    * @see net.mlw.vlh.ValueListAdapter#getValueList(java.lang.String,
    *      net.mlw.vlh.ValueListInfo)
    */
   public ValueList getValueList(String name, ValueListInfo info)
   {
      if (info.getSortingColumn() == null)
      {
         info.setPrimarySortColumn(getDefaultSortColumn());
         info.setPrimarySortDirection(getDefaultSortDirectionInteger());
      }

      int numberPerPage = info.getPagingNumberPer();

      if (numberPerPage == Integer.MAX_VALUE)
      {
         numberPerPage = getDefaultNumberPerPage();
         info.setPagingNumberPer(numberPerPage);
      }

      Connection connection = null;
      PreparedStatement statement = null;
      ResultSet result = null;

      try
      {
         boolean doSqlPaging = ((getAdapterType() & DO_PAGE) == 0);

         connection = connectionCreator.createConnection();

         StringBuffer query = (sqlPagingSupport != null) ? sqlPagingSupport.getPagedQuery(sql) : new StringBuffer(sql);
         statement = statementBuilder.generate(connection, query, info.getFilters(), sqlPagingSupport == null && doSqlPaging);

         if (LOGGER.isDebugEnabled())
         {
            LOGGER.debug(query.toString());
         }
         if (showSql)
         {
            System.out.println("sql: " + query.toString());
         }
         result = getResultSet(statement, info);

         if (sqlPagingSupport != null)
         {
            PreparedStatement countStatement = null;
            ResultSet countResult = null;

            try
            {
               StringBuffer countQuery = sqlPagingSupport.getCountQuery(sql);
               countStatement = statementBuilder.generate(connection, countQuery, info.getFilters(), false);
               if (showSql)
               {
                  System.out.println("count sql: " + countQuery.toString());
               }
               
               countResult = countStatement.executeQuery();
               if (countResult.next())
               {
                  info.setTotalNumberOfEntries(countResult.getInt(1));
               }
            }
            finally
            {
               JdbcUtil.close(countResult, countStatement, null);
            }

         }
         else if (doSqlPaging)
         {
            result.last();
            int totalRows = result.getRow();
            info.setTotalNumberOfEntries(totalRows);

            if (numberPerPage == 0)
            {
               numberPerPage = getDefaultNumberPerPage();
            }

            int pageNumber = info.getPagingPage();
            if (pageNumber > 1)
            {
               if ((pageNumber - 1) * numberPerPage > totalRows)
               {
                  pageNumber = ((totalRows - 1) / numberPerPage) + 1;
                  info.setPagingPage(pageNumber);
               }
            }

            if (pageNumber > 1)
            {
               result.absolute((pageNumber - 1) * numberPerPage);
            }
            else
            {
               result.beforeFirst();
            }
         }

         List list = processResultSet(name, result, (doSqlPaging) ? numberPerPage : Integer.MAX_VALUE, info);

         if (!doSqlPaging)
         {
            info.setTotalNumberOfEntries(list.size());
         }

         return new DefaultListBackedValueList(list, info);

      }
      catch (Exception e)
      {
         LOGGER.error(e);
         throw new RuntimeException(e);
      }
      finally
      {
         connectionCreator.close(result, statement, connection);
      }
   }

   /**
    * @param statement
    * @param info This info will be set to validator.
    * @return ResultSet (validator is null) or ResultSetDecorator (validator is
    *         not null)
    * @throws SQLException
    * @see net.mlw.vlh.adapter.util.ObjectValidator
    * @see net.mlw.vlh.adapter.jdbc.objectWrapper.ResultSetDecorator
    */
   private ResultSet getResultSet(PreparedStatement statement, ValueListInfo info) throws SQLException
   {
      if (_validator == null)
      {
         return statement.executeQuery();
      }
      else
      {
         _validator.setValueListInfo(info);
         return new ResultSetDecorator(statement.executeQuery(), _validator);
      }
   }

   /**
    * This method takes the result and puts the VOs in the List.
    *
    * @param result The ResultSet to interate through.
    * @param info is ussually constant during this process, you can use it for
    *            passing additional parameters from controler. (Like in
    *            DefaultWrapperAdapter)
    * @return The List of VOs.
    */
   public abstract List processResultSet(String name, ResultSet result, int numberPerPage, ValueListInfo info) throws SQLException;

   /**
    * @return Returns the dataSource.
    */
   public DataSource getDataSource()
   {
      return connectionCreator.getDataSource();
   }

   /**
    * @param dataSource The dataSource to set.
    */
   public void setDataSource(DataSource dataSource)
   {
      connectionCreator.setDataSource(dataSource);
   }

   /**
    * @return Returns the sql.
    */
   public String getSql()
   {
      return sql;
   }

   /**
    * @param sql The sql to set.
    */
   public void setSql(String sql)
   {
      this.sql = sql;
   }

   /**
    * @return Returns the statementBuilder.
    */
   public StatementBuilder getStatementBuilder()
   {
      return statementBuilder;
   }

   /**
    * @param statementBuilder The statementBuilder to set.
    */
   public void setStatementBuilder(StatementBuilder statementBuilder)
   {
      this.statementBuilder = statementBuilder;
   }

   /**
    * @return Returns the showSql.
    */
   public boolean isShowSql()
   {
      return showSql;
   }

   /**
    * @param showSql The showSql to set.
    */
   public void setShowSql(boolean showSql)
   {
      this.showSql = showSql;
   }

   /**
    * @return Returns the objectValidator.
    */
   public ObjectValidator getValidator()
   {
      return _validator;
   }

   /**
    * <p>
    * If is set to not null value, it uses a special
    * <code>ResultsSetDecorator<code>, that enable or
    * disable filtering objects in the final valuelist.
    * </p>
    * <h4>NOTE:</h4>
    * <p>
    * It respects the total count of entries that overlap your paged
    * list. Simply spoken it supports features such as paging.
    * </p>
    * @param objectValidator The objectValidator to set.
    * The null value means that validator is disabled.
    * @see net.mlw.vlh.adapter.util.ObjectValidator
    * @see net.mlw.vlh.adapter.jdbc.objectWrapper.ResultSetDecorator
    */
   public void setValidator(ObjectValidator objectValidator)
   {
      this._validator = objectValidator;
   }

   /**
    * @return Returns the connectionCreator.
    */
   public ConnectionCreator getConnectionCreator()
   {
      return connectionCreator;
   }

   /**
    * @param connectionCreator The connectionCreator to set.
    */
   public void setConnectionCreator(ConnectionCreator connectionCreator)
   {
      this.connectionCreator = connectionCreator;
   }

   public SqlPagingSupport getSqlPagingSupport()
   {
      return sqlPagingSupport;
   }

   public void setSqlPagingSupport(SqlPagingSupport sqlPagingSupport)
   {
      this.sqlPagingSupport = sqlPagingSupport;
   }
}